﻿using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace ChivalryUnity.Mvc
{
    public class MvcFacade
    {
        private static MvcFacade s_instance;
        public static MvcFacade Instance
        {
            get
            {
                if (s_instance == null)
                {
                    Debug.LogError("facade没有初始化,需要先调用Initialize");
                }
                return s_instance;
            }
        }
        public static void Initialize()
        {
            if (s_instance == null)
            {
                s_instance = new MvcFacade();

                foreach (var type in Assembly.GetCallingAssembly().GetTypes())
                {
                    s_instance.LoadContexts(type);
                }
            }
        }

        public delegate void MessageHandler<T>(T value);
        public delegate void MessageHandlerNoParameter();

        private Queue<MessageCommand> m_msgCmdQueue;
        private Dictionary<string, List<MvcListenerToken>> m_listeners;
        private Dictionary<Type, MvcContext> m_contexts;

        private MvcFacade()
        {
            m_msgCmdQueue = new Queue<MessageCommand>();
            m_listeners = new Dictionary<string, List<MvcListenerToken>>();
            m_contexts = new Dictionary<Type, MvcContext>();
        }

        /// <summary>
        /// 获取Context
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T RetriveContext<T>() where T : MvcContext
        {
            MvcContext context;
            m_contexts.TryGetValue(typeof(T), out context);
            return context as T;
        }
        public MvcContext RetriveContext(Type contextType)
        {
            MvcContext context;
            m_contexts.TryGetValue(contextType, out context);
            return context;
        }

        private void LoadContexts(Type type)
        {
            if (type.IsSubclassOf(typeof(MvcContext)))
            {
                if (m_contexts.ContainsKey(type))
                    return;

                object context = Activator.CreateInstance(type);
                if (context != null)
                {
                    m_contexts.Add(type, context as MvcContext);
                }
            }
        }

        private List<MvcListenerToken> GetMethodInfoList(string msg)
        {
            List<MvcListenerToken> list;
            if (!m_listeners.TryGetValue(msg, out list))
            {
                list = new List<MvcListenerToken>();
                m_listeners.Add(msg, list);
            }
            return list;
        }

        public MvcListenerToken AddListener(string msg, MessageHandlerNoParameter listener)
        {
            return AddListener(msg, listener.Target, listener.Method);
        }
        public MvcListenerToken AddListener<T>(string msg, MessageHandler<T> listener)
        {
            return AddListener(msg, listener.Target, listener.Method);
        }
        public MvcListenerToken AddListener(string msg, object self, MethodInfo listener)
        {
            MvcListenerToken token = new MvcListenerToken() { msg = msg, self = self, listener = listener };
            AddListenerCommand cmd = new AddListenerCommand(this, token);
            m_msgCmdQueue.Enqueue(cmd);
            TryExecuteMsgCmdQueue();
            return token;
        }

        public void RemoveListener(MvcListenerToken token)
        {
            RemoveListenerCommand cmd = new RemoveListenerCommand(this, token);
            m_msgCmdQueue.Enqueue(cmd);
            TryExecuteMsgCmdQueue();
        }

        public void Dispatch(string msg)
        {
            Dispatch(msg, null);
        }

        public void Dispatch(string msg, object value)
        {
            DispatchMessageCommand cmd = new DispatchMessageCommand(this, msg, value);
            m_msgCmdQueue.Enqueue(cmd);
            TryExecuteMsgCmdQueue();
        }

        private bool m_executing;
        private void TryExecuteMsgCmdQueue()
        {
            if (m_executing)
                return;
            m_executing = true;
            while (m_msgCmdQueue.Count > 0)
            {
                m_msgCmdQueue.Dequeue().Execute();
            }
            m_executing = false;
        }



        abstract class MessageCommand
        {
            internal abstract void Execute();
        }

        class AddListenerCommand : MessageCommand
        {
            private MvcFacade m_MvcFacade;
            private MvcListenerToken m_token;

            public AddListenerCommand(MvcFacade mvcFacade, MvcListenerToken token)
            {
                m_MvcFacade = mvcFacade;
                m_token = token;
            }

            internal override void Execute()
            {
                m_MvcFacade.GetMethodInfoList(m_token.msg).Add(m_token);
            }
        }
        class RemoveListenerCommand : MessageCommand
        {
            private MvcFacade m_mvcFacade;
            private MvcListenerToken m_token;

            public RemoveListenerCommand(MvcFacade mvcFacade, MvcListenerToken token)
            {
                m_mvcFacade = mvcFacade;
                m_token = token;
            }

            internal override void Execute()
            {
                m_mvcFacade.GetMethodInfoList(m_token.msg).Remove(m_token);
            }
        }

        class DispatchMessageCommand : MessageCommand
        {
            private MvcFacade m_mvcFacade;
            private string m_msg;
            private object m_value;

            public DispatchMessageCommand(MvcFacade mvcFacade, string msg, object value)
            {
                m_mvcFacade = mvcFacade;
                m_msg = msg;
                m_value = value;
            }

            internal override void Execute()
            {
                foreach (var data in m_mvcFacade.GetMethodInfoList(m_msg))
                {
                    MethodInfo methodInfo = data.listener;
                    if (methodInfo.GetParameters().Length == 0)
                    {
                        methodInfo.Invoke(data.self, null);
                    }
                    else
                    {
                        ParameterInfo[] parameters = methodInfo.GetParameters();
                        if (parameters.Length > 1)
                        {
                            Debug.LogErrorFormat("消息参数类型错误,{0} 消息最多只支持一个参数", m_msg);
                            return;
                        }

                        if (m_value == null)
                        {
                            Debug.LogErrorFormat("消息参数类型错误,{0} 消息没有参数", m_msg);
                            return;
                        }

                        if (!parameters[0].ParameterType.IsAssignableFrom(m_value.GetType()))
                        {
                            Debug.LogErrorFormat("消息参数类型错误,{0} 消息期待的参数类型为:{1}", m_msg, m_value.GetType().Name);
                            return;
                        }

                        data.listener.Invoke(data.self, new object[] { m_value });
                    }
                }
            }
        }
    }
}